// from https://github.com/Microsoft/DirectX-Graphics-Samples/blob/master/MiniEngine/Core/Shaders/ColorSpaceUtility.hlsli

// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
//
// GLSL port by Zhirnov Andrey.

#ifdef __cplusplus
# pragma once
#endif

#include "Math.glsl"

//
// Gamma ramps and encoding transfer functions
//
// Orthogonal to color space though usually tightly coupled.  For instance, sRGB is both a
// color space (defined by three basis vectors and a white point) and a gamma ramp.  Gamma
// ramps are designed to reduce perceptual error when quantizing floats to integers with a
// limited number of bits.  More variation is needed in darker colors because our eyes are
// more sensitive in the dark.  The way the curve helps is that it spreads out dark values
// across more code words allowing for more variation.  Likewise, bright values are merged
// together into fewer code words allowing for less variation.
//
// The sRGB curve is not a true gamma ramp but rather a piecewise function comprising a linear
// section and a power function.  When sRGB-encoded colors are passed to an LCD monitor, they
// look correct on screen because the monitor expects the colors to be encoded with sRGB, and it
// removes the sRGB curve to linearize the values.  When textures are encoded with sRGB--as many
// are--the sRGB curve needs to be removed before involving the colors in linear mathematics such
// as physically based lighting.

// Approximately pow(x, 1.0 / 2.2)
float   ApplySRGBCurve (float  x)		{ return x < 0.0031308 ? 12.92 * x : 1.055 * Pow(x, 1.0 / 2.4) - 0.055; }
float3  ApplySRGBCurve (float3 v)		{ return Select( v < 0.0031308,  (12.92 * v),  (1.055 * Pow(v, 1.0 / 2.4) - 0.055) ); }
float4  ApplySRGBCurve (float4 v)		{ return float4( ApplySRGBCurve( v.rgb ), v.a ); }

// Approximately pow(x, 2.2)
float   RemoveSRGBCurve (float  x)		{ return x < 0.04045 ? x / 12.92 : Pow( (x + 0.055) / 1.055, 2.4 ); }
float3  RemoveSRGBCurve (float3 v)		{ return Select( v < 0.04045,  (v / 12.92),  Pow( (v + 0.055) / 1.055, 2.4 ) ); }
float4  RemoveSRGBCurve (float4 v)		{ return float4( RemoveSRGBCurve( v.rgb ), v.a ); }


// These functions avoid pow() to efficiently approximate sRGB with an error < 0.4%.
float   ApplySRGBCurve_Fast (float  x)	{ return x < 0.0031308 ? 12.92 * x : 1.13005 * sqrt(x - 0.00228) - 0.13448 * x + 0.005719; }
float3  ApplySRGBCurve_Fast (float3 v)	{ return Select( v < 0.0031308,  (12.92 * v),  (1.13005 * Sqrt(v - 0.00228) - 0.13448 * v + 0.005719) ); }
float4  ApplySRGBCurve_Fast (float4 v)	{ return float4( ApplySRGBCurve_Fast( v.rgb ), v.a ); }

float   RemoveSRGBCurve_Fast (float  x)	{ return x < 0.04045 ? x / 12.92 : -7.43605 * x - 31.24297 * Sqrt(-0.53792 * x + 1.279924) + 35.34864; }
float3  RemoveSRGBCurve_Fast (float3 v)	{ return Select( v < 0.04045,  (v / 12.92),  (-7.43605 * v - 31.24297 * Sqrt(-0.53792 * v + 1.279924) + 35.34864) ); }
float4  RemoveSRGBCurve_Fast (float4 v)	{ return float4( RemoveSRGBCurve_Fast( v.rgb ), v.a ); }


// The OETF recommended for content shown on HDTVs.  This "gamma ramp" may increase contrast as
// appropriate for viewing in a dark environment.  Always use this curve with Limited RGB as it is
// used in conjunction with HDTVs.
float   ApplyREC709Curve (float  x)		{ return x < 0.0181 ? 4.5 * x : 1.0993 * Pow(x, 0.45) - 0.0993; }
float3  ApplyREC709Curve (float3 v)		{ return Select( v < 0.0181,  (4.5 * v),  (1.0993 * Pow(v, 0.45) - 0.0993) ); }
float4  ApplyREC709Curve (float4 v)		{ return float4( ApplyREC709Curve( v.rgb ), v.a ); }

float   RemoveREC709Curve (float  x)	{ return x < 0.08145 ? x / 4.5 : Pow((x + 0.0993) / 1.0993, 1.0 / 0.45); }
float3  RemoveREC709Curve (float3 v)	{ return Select( v < 0.08145,  (v / 4.5),  Pow( (v + 0.0993) / 1.0993, 1.0 / 0.45 )); }
float4  RemoveREC709Curve (float4 v)	{ return float4( RemoveREC709Curve( v.rgb ), v.a ); }


// This is the new HDR transfer function, also called "PQ" for perceptual quantizer.  Note that REC2084
// does not also refer to a color space.  REC2084 is typically used with the REC2020 color space.
float3  ApplyREC2084Curve (float3 L)
{
	float  m1 = 2610.0 / 4096.0 / 4;
	float  m2 = 2523.0 / 4096.0 * 128;
	float  c1 = 3424.0 / 4096.0;
	float  c2 = 2413.0 / 4096.0 * 32;
	float  c3 = 2392.0 / 4096.0 * 32;
	float3 Lp = Pow( L, float3(m1) );
	return Pow( (c1 + c2 * Lp) / (1 + c3 * Lp), float3(m2) );
}

float3  RemoveREC2084Curve (float3 N)
{
	float  m1 = 2610.0 / 4096.0 / 4;
	float  m2 = 2523.0 / 4096.0 * 128;
	float  c1 = 3424.0 / 4096.0;
	float  c2 = 2413.0 / 4096.0 * 32;
	float  c3 = 2392.0 / 4096.0 * 32;
	float3 Np = Pow( N, float3(1.0 / m2) );
	return Pow( max( Np - c1, float3(0.0) ) / (c2 - c3 * Np), float3(1.0 / m1) );
}

//
// Color space conversions
//
// These assume linear (not gamma-encoded) values.  A color space conversion is a change
// of basis (like in Linear Algebra).  Since a color space is defined by three vectors--
// the basis vectors--changing space involves a matrix-vector multiplication.  Note that
// changing the color space may result in colors that are "out of bounds" because some
// color spaces have larger gamuts than others.  When converting some colors from a wide
// gamut to small gamut, negative values may result, which are inexpressible in that new
// color space.
//
// It would be ideal to build a color pipeline which never throws away inexpressible (but
// perceivable) colors.  This means using a color space that is as wide as possible.  The
// XYZ color space is the neutral, all-encompassing color space, but it has the unfortunate
// property of having negative values (specifically in X and Z).  To correct this, a further
// transformation can be made to X and Z to make them always positive.  They can have their
// precision needs reduced by dividing by Y, allowing X and Z to be packed into two UNORM8s.
// This color space is called YUV for lack of a better name.
//

// Note:  Rec.709 and sRGB share the same color primaries and white point.  Their only difference
// is the transfer curve used.

float3  REC709toREC2020 (float3 RGB709)
{
	const float3x3 ConvMat = float3x3(
		0.627402, 0.329292, 0.043306,
		0.069095, 0.919544, 0.011360,
		0.016394, 0.088028, 0.895578
	);
	return ConvMat * RGB709;
}

float3  REC2020toREC709 (float3 RGB2020)
{
	const float3x3 ConvMat = float3x3(
		1.660496, -0.587656, -0.072840,
		-0.124547, 1.132895, -0.008348,
		-0.018154, -0.100597, 1.118751
	);
	return ConvMat * RGB2020;
}

float3  REC709toDCIP3 (float3 RGB709)
{
	const float3x3 ConvMat = float3x3(
		0.822458, 0.177542, 0.000000,
		0.033193, 0.966807, 0.000000,
		0.017085, 0.072410, 0.910505
	);
	return ConvMat * RGB709;
}

float3  DCIP3toREC709 (float3 RGBP3)
{
	const float3x3 ConvMat = float3x3(
		1.224947, -0.224947, 0.000000,
		-0.042056, 1.042056, 0.000000,
		-0.019641, -0.078651, 1.098291
	);
	return ConvMat * RGBP3;
}
//-----------------------------------------------------------------------------



// from https://github.com/microsoft/DirectX-Graphics-Samples/blob/master/MiniEngine/Core/Shaders/ShaderUtility.hlsli


// 10-bit should range from 64 to 940
float3  RGBFullToLimited10bit (float3 x)
{
	return Saturate(x) * 876.0 / 1023.0 + 64.0 / 1023.0;
}


float3  ApplyDisplayProfile (const float3 color, const int displayFormat)
{
	switch ( displayFormat )
	{
		default:
		case COLOR_FORMAT_LINEAR :			return color;
		case COLOR_FORMAT_sRGB_FULL :		return ApplySRGBCurve( color );
		case COLOR_FORMAT_sRGB_LIMITED :	return RGBFullToLimited10bit( ApplySRGBCurve( color ));
		case COLOR_FORMAT_Rec709_FULL :		return ApplyREC709Curve( color );
		case COLOR_FORMAT_Rec709_LIMITED :	return RGBFullToLimited10bit( ApplyREC709Curve( color ));
		case COLOR_FORMAT_HDR10 :			return ApplyREC2084Curve( REC709toREC2020( color ));
	};
}



